﻿using System;
using System.Collections.Generic;
using System.Linq;
using Ewk.MachineLearning.Genetic.Programming.Program;

namespace Ewk.MachineLearning.Genetic.Programming.Engine
{
    public class Process
    {
        private readonly EngineConfiguration _engineConfiguration;
        private readonly ProblemDefinition _problemDefinition;

        private readonly PopulationFactory _populationFactory;

        #region Constructors

        public Process(ProblemDefinition problemDefinition)
            : this(problemDefinition, new EngineConfiguration())
        {
        }

        public Process(ProblemDefinition problemDefinition, EngineConfiguration engineConfiguration)
        {
            _problemDefinition = problemDefinition;
            _engineConfiguration = engineConfiguration;

            _populationFactory = new PopulationFactory(_engineConfiguration, _problemDefinition);
        }

        #endregion

        public ProblemSolution GetSolution()
        {
            // Generate Initial population
            var population = _populationFactory.CreateInitialPopulation().ToList();

            bool canImprove;

            do
            {
                var populationResults = GetPopulationResults(population);
                var fitnessCalculator = _problemDefinition.FitnessCalculator;

                var populationFitness = new Dictionary<Node, double>();
                foreach (var individualResults in populationResults)
                {
                    var fitness = fitnessCalculator.Calculate(individualResults);
                    populationFitness.Add(individualResults.Node, fitness);
                }

                canImprove = fitnessCalculator.CanImprove(populationFitness.Values);
                population = CreateNextGeneration(population).ToList();
            } while (canImprove);
            
            return new ProblemSolution();
        }

        private IEnumerable<IndividualResults> GetPopulationResults(IEnumerable<Node> population)
        {
            return population
                .Select(node =>
                        new IndividualResults(
                            node,
                            _problemDefinition.TestSet
                                .Select(exemplar =>
                                    new Result(exemplar.ExpectedOuput,
                                        GetIndividualResult(node, exemplar.Input)))
                            ));
        }

        private dynamic GetIndividualResult(Node root, IEnumerable<dynamic> parameters)
        {
            parameters = parameters.ToList();

            var i = 0;
            foreach (var variableNode in _populationFactory.VariableNodes)
            {
                variableNode.SetValue(parameters.ElementAt(i));
                i++;
            }

            return root.GetValue();
        }

        private IEnumerable<Node> CreateNextGeneration(IEnumerable<Node> population)
        {
            throw new NotImplementedException();
        }
    }
}